package com.debao.common.wechat;

import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.debao.common.config.WechatMiniProgramConfig;
import com.debao.common.constant.WechatConstants;
import com.debao.common.model.WechatTemplateVo;
import com.debao.common.model.WxMssVo;
import org.apache.commons.lang3.StringUtils;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.InputStream;
import java.rmi.ServerException;
import java.util.*;

public class WechatUtils {

    // API错误码常量
    private static final int SUCCESS_ERR_CODE = 0;
    private static final Logger log = LoggerFactory.getLogger(WechatUtils.class);
    public static String TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token";
    public static String ACCOUNT_URL = "https://api.weixin.qq.com/cgi-bin/token";
    // 获取手机号请求
    private static final String PHONE_URL = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=%s";
    // 授权地址
    private static final String JSCODESESSION_URL = "https://api.weixin.qq.com/sns/jscode2session";

    private static final String URLLINK_URL = "https://api.weixin.qq.com/wxa/generate_urllink?access_token=%s";


    // 定义类常量，用于提高代码的可维护性
    private static final String APPID_KEY = "appid";
    private static final String SECRET_KEY = "secret";
    private static final String JS_CODE_KEY = "js_code";
    private static final String GRANT_TYPE_KEY = "grant_type";
    private static final String AUTHORIZATION_CODE = "authorization_code";
    private static final String EMPTY_STRING = "openid";

    // 订阅消息发送
    private static String sendUrl = "https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=%s";

    //通过 URL Link 进入的小程序页面路径，必须是已经发布的小程序存在的页面，不可携带 query 。path 为空时会跳转小程序主页
    private static final String PATH = "path";
    // 携带的参数 a=2
    private static final String QUERY = "query";
    //生成的 URL Link 类型，到期失效：true，永久有效：false
    private static final String IS_EXPIRE = "is_expire";
    //小程序 URL Link 失效类型，失效时间：0，失效间隔天数：1
    private static final String EXPIRE_TYPE = "expire_type";
    //到期失效的URL Link的失效间隔天数。生成的到期失效URL Link在该间隔时间到达前有效。最长间隔天数为365天。expire_type 为 1 必填
    private static final String EXPIRE_INTERVAL = "expire_interval";
    private static final String URL_LINK = "url_link";

    // 图片安全检测
    public final static String IMG_SEC_CHECK = "https://api.weixin.qq.com/wxa/img_sec_check";
    // 文本内容检测
    public final static String MSG_SEC_CHECK = "https://api.weixin.qq.com/wxa/msg_sec_check?access_token=ACCESS_TOKEN";

    public static int ZERO = 0;
    public static String SUGGEST = "risky";


    @Resource
    private WechatMiniProgramConfig config;


    /**
     * 获取小程序token
     *
     * @return
     */
    public static String getToken(String appId, String appSecret) {
        if (appId.isEmpty() || appSecret.isEmpty()) {
            throw new IllegalArgumentException("Code and token must not be null.");
        }
        try {
            Map<String, Object> requestParameters = new HashMap<>();
            requestParameters.put("grant_type", "client_credential");
            requestParameters.put("appid", appId);
            requestParameters.put("secret", appSecret);

            JSONObject response = JSONUtil.parseObj(HttpUtil.get(TOKEN_URL, requestParameters));
            String accessToken = response.getStr("access_token");
            if (StringUtils.isEmpty(accessToken)) {
                throw new RuntimeException("Failed to retrieve access token.");
            }

            return accessToken;
        } catch (Exception e) {
            throw new RuntimeException("Exception occurred while trying to get token: " + e.getMessage());
        }
    }

    /**
     * 获取公众号token
     *
     * @return
     */
    public static String getAccountToken(String appId, String appSecret) {
        if (appId.isEmpty() || appSecret.isEmpty()) {
            throw new IllegalArgumentException("Code and token must not be null.");
        }
        try {
            Map<String, Object> requestParameters = new HashMap<>();
            requestParameters.put("grant_type", "client_credential");
            requestParameters.put("appid", appId);
            requestParameters.put("secret", appSecret);

            JSONObject response = JSONUtil.parseObj(HttpUtil.get(ACCOUNT_URL, requestParameters));
            String accessToken = response.getStr("access_token");
            if (StringUtils.isEmpty(accessToken)) {
                throw new RuntimeException("Failed to retrieve access token.");
            }

            return accessToken;
        } catch (Exception e) {
            throw new RuntimeException("Exception occurred while trying to get token: " + e.getMessage());
        }
    }


    /**
     * 微信获取手机号码
     *
     * @param code  验证码
     * @param token 安全令牌
     * @return 手机号码
     * @throws AuthenticationException 验证失败时抛出异常
     * @throws IOException             网络请求异常
     */
    public static String authenticatePhoneNumber(String code, String token) throws AuthenticationException, IOException {
        if (code == null || token == null) {
            throw new IllegalArgumentException("Code and token must not be null.");
        }

        Map<String, String> params = new HashMap<>(1); // 初始化容量，减少resize
        params.put("code", code);
        // 执行HTTP POST请求
        ResponseEntity<JSONObject> result = new RestTemplate().postForEntity(String.format(PHONE_URL, token), params, JSONObject.class);

        // 解析响应结果
        int errCode = result.getBody().getInt("errcode");
        String errMsg = result.getBody().getStr("errmsg");

        // 错误处理
        if (errCode != SUCCESS_ERR_CODE) {
            // 抛出更具体的异常，提供更多的错误信息
            throw new AuthenticationException("Authentication failed: " + errMsg);
        }

        // 获取手机号码
        JSONObject phoneInfo = result.getBody().getJSONObject("phone_info");
        String phoneNumber = phoneInfo.getStr("phoneNumber");

        if (phoneNumber == null || phoneNumber.isEmpty()) {
            // 处理结果中不存在手机号码的情况
            throw new AuthenticationException("Phone number not found in the response.");
        }

        return phoneNumber;
    }


    public static String getOpenId(String code, String appId, String secret) throws AuthenticationException {

        if (code == null || code.isEmpty()) {
            throw new IllegalArgumentException("Code cannot be null or empty.");
        }
        Map<String, Object> requestParameters = new HashMap<>();
        requestParameters.put(APPID_KEY, appId);
        requestParameters.put(SECRET_KEY, secret);
        requestParameters.put(JS_CODE_KEY, code);
        requestParameters.put(GRANT_TYPE_KEY, AUTHORIZATION_CODE);
        try {
            String res = HttpUtil.post(JSCODESESSION_URL, requestParameters);
            JSONObject result = JSONUtil.parseObj(res);
            if (!result.isEmpty() && result.containsKey(EMPTY_STRING)) {
                return result.getStr(EMPTY_STRING);
            }
            throw new AuthenticationException("Empty or null response received while fetching openId.");
        } catch (Exception e) {
            throw new AuthenticationException("Exception occurred while fetching openId: " + e.getMessage());
        }
    }


    public static JSONObject getSessionData(String code, String appId, String secret) throws AuthenticationException {

        if (code == null || code.isEmpty()) {
            throw new IllegalArgumentException("Code cannot be null or empty.");
        }
        Map<String, Object> requestParameters = new HashMap<>();
        requestParameters.put(APPID_KEY, appId);
        requestParameters.put(SECRET_KEY, secret);
        requestParameters.put(JS_CODE_KEY, code);
        requestParameters.put(GRANT_TYPE_KEY, AUTHORIZATION_CODE);
        try {
            String res = HttpUtil.post(JSCODESESSION_URL, requestParameters);
            JSONObject sessionData = JSONUtil.parseObj(res);
            if (!sessionData.isEmpty() && sessionData.containsKey(EMPTY_STRING)) {
                return sessionData;
            }
            throw new AuthenticationException("Empty or null response received while fetching openId.");
        } catch (Exception e) {
            throw new AuthenticationException("Exception occurred while fetching openId: " + e.getMessage());
        }
    }


    // 自定义异常类
    public static class AuthenticationException extends Exception {
        public AuthenticationException(String message) {
            super(message);
        }
    }


    /**
     * 生成锻炼【短信、邮件、网页等】
     *
     * @param token
     * @return
     * @throws AuthenticationException
     */
    public static String createUrlLink(String token, String path, String query) throws AuthenticationException {
        if (token == null || token.isEmpty()) {
            throw new IllegalArgumentException("token cannot be null or empty.");
        }
        Map<String, Object> requestParameters = new HashMap<>();
        requestParameters.put(PATH, path);
        requestParameters.put(QUERY, query);
        requestParameters.put(IS_EXPIRE, false);
        requestParameters.put(EXPIRE_TYPE, 1);
        requestParameters.put(EXPIRE_INTERVAL, 1);
        try {
            String res = HttpUtil.post(String.format(URLLINK_URL, token), requestParameters);
            JSONObject result = JSONUtil.parseObj(res);
            if (!result.isEmpty() && result.containsKey(URL_LINK)) {
                return result.getStr(URL_LINK);
            }
            throw new AuthenticationException("Empty or null response received while fetching url_link.");
        } catch (Exception e) {
            throw new AuthenticationException("Exception occurred while fetching url_link: " + e.getMessage());
        }
    }

    /**
     * 小程序订阅消息推送
     *
     * @param vo
     * @return
     */
    public static JSONObject postTemplateData(WechatTemplateVo vo) {
        //发送订阅消息
        RestTemplate restTemplate = new RestTemplate();
        // url
        String url = String.format(sendUrl, vo.getToken());
        // 请求
        ResponseEntity<JSONObject> responseEntity = restTemplate.postForEntity(url, new WxMssVo().setTouser(vo.getOpenid()).setTemplate_id(vo.getTemplateId()).setPage(vo.getPage()).setData(vo.getData()), JSONObject.class);

        return responseEntity.getBody();
    }

    /**
     * 解析微信发来的请求(xml)
     *
     * @param request
     * @return
     * @throws Exception
     */
    @SuppressWarnings({"unchecked"})
    public static Map<String, String> parseXml(HttpServletRequest request) throws Exception {
        // 将解析结果存储在HashMap中
        Map<String, String> map = new HashMap<String, String>();

        // 从request中取得输入流
        InputStream inputStream = request.getInputStream();
        // 读取输入流
        SAXReader reader = new SAXReader();
        Document document = reader.read(inputStream);
        // 得到xml根元素
        Element root = document.getRootElement();
        // 得到根元素的所有子节点
        List<Element> elementList = root.elements();
        // 遍历所有子节点
        for (Element e : elementList) {
            map.put(e.getName(), e.getText());
        }
        // 释放资源
        inputStream.close();
        inputStream = null;
        return map;
    }

    public static String mapToXML(Map map) {
        StringBuffer sb = new StringBuffer();
        sb.append("<xml>");
        mapToXML2(map, sb);
        sb.append("</xml>");
        try {
            return sb.toString();
        } catch (Exception e) {
        }
        return null;
    }

    private static void mapToXML2(Map map, StringBuffer sb) {
        Set set = map.keySet();
        for (Iterator it = set.iterator(); it.hasNext(); ) {
            String key = (String) it.next();
            Object value = map.get(key);
            if (null == value) {
                value = "";
            }
            if (value.getClass().getName().equals("java.util.ArrayList")) {
                ArrayList list = (ArrayList) map.get(key);
                sb.append("<" + key + ">");
                for (int i = 0; i < list.size(); i++) {
                    HashMap hm = (HashMap) list.get(i);
                    mapToXML2(hm, sb);
                }
                sb.append("</" + key + ">");

            } else {
                if (value instanceof HashMap) {
                    sb.append("<" + key + ">");
                    mapToXML2((HashMap) value, sb);
                    sb.append("</" + key + ">");
                } else {
                    sb.append("<" + key + "><![CDATA[" + value + "]]></" + key + ">");
                }

            }

        }
    }

    /**
     * 回复文本消息
     *
     * @param requestMap
     * @param content
     * @return
     */
    public static String sendTextMsg(Map<String, String> requestMap, String content) {

        Map<String, Object> map = new HashMap<String, Object>();
        map.put("ToUserName", requestMap.get(WechatConstants.FromUserName));
        map.put("FromUserName", requestMap.get(WechatConstants.ToUserName));
        map.put("MsgType", WechatConstants.MsgType_Text);
        map.put("CreateTime", System.currentTimeMillis());
        map.put("Content", content);
        return mapToXML(map);
    }

    /**
     * 发布文字检测结果
     *
     * @param accessToken
     * @param content
     */
    public static void weiXinCheckContent(String accessToken, String content, String openid) {
        if (StringUtils.isEmpty(accessToken) || StringUtils.isEmpty(content)) {
            throw new RuntimeException("accessToken或content不能为空");
        }
        try {
            String url = MSG_SEC_CHECK.replace("ACCESS_TOKEN", accessToken);
            Map<String, Object> map = new HashMap<>();
            map.put("content",content);
            map.put("openid", openid);
            map.put("scene", 3);
            map.put("version", 2);
            String responseStr = HttpUtil.createPost(url).body(JSONUtil.toJsonPrettyStr(map), "application/json").execute().body();
            JSONObject response = JSONUtil.parseObj(responseStr);
            System.out.println(responseStr);
            int errcode = response.getInt("errcode");
            if (errcode != ZERO) {
                throw new ServerException("请求失败，错误码: " + errcode);
            }
            String suggest = response.getJSONObject("result").getStr("suggest");
            int label = response.getJSONObject("result").getInt("label");
            // 判断内容是否被标记为有风险
            if (label != 100 || SUGGEST.equals(suggest)) {
                throw new ServerException("存在敏感信息，无法保存。建议: " + suggest + "，标签: " + label);
            }
        } catch (IOException e) {
            throw new RuntimeException("存在敏感信息，无法保存"+e.getMessage());
        }
    }
}
